#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#include "general.h"
#include "stack.h"
#include "scheduler.h"

extern Statistics stat;

/* schedule a task on stack*/
void schedule (Stack s, void (*routine)(void *), void * arg)
{
	Work w;
	StackElement_T d;
	//int fnum = STACK_DEPTH + 1;
	unsigned long long start, stop;

	start = rdtsc();
	w = malloc(sizeof(Work_T));
	if (w == NULL)
	{
		fprintf(stderr, "schedule() unable to allocate memory!\n");
		exit(-1);
	}
	w->routine = routine;
	w->input = arg;
	d.element = (void *) w;
	
	push(s, &d);  
	stop = rdtsc();
}

void terminateProcessors (Stack s, int num_thread)
{
	int i;
	Work w;
	StackElement_T d;
	
	/* terminate all processors */
	for (i = 0; i < num_thread; i++)
	{
		w = malloc(sizeof(Work_T));
		w->routine = &pthread_exit;
		w->input = NULL;
		d.element = (void *) w;
		push(s, &d);
	}
}

/* processor thread */
void * processor (void * input)
{
	ProcArgs pargs = (ProcArgs) input;;
	long tid;
	Stack s;
	StackElement_T d;
	Work w;
	void (*routine)(void *);
	void * rargs;
	ProcStatus pstat;
	unsigned long long start, stop;

	pstat = pargs->pstat;
	tid = pargs->tid;
	s = pargs->s;

	//pthread_barrier_wait(&pstat->bar);

	while (1)
	{
		start = rdtsc();
		pop(s, &d);
		stop = rdtsc();
		stat.dequeue_time[tid] += stop - start;
		stat.num_dequeue[tid]++;

		start = rdtsc();
		w = (Work) d.element;
		if (w == NULL) continue; //pthread_exit(NULL);

		routine = w->routine;
		rargs = w->input;
		free(w);
		if ((void*)routine == &pthread_exit)
		{
			stop = rdtsc();
			//printf("t%ld %lld\n", tid, stop - start);
		}
		stop = rdtsc();
		stat.schedule_overhead[tid] += stop - start;

		start = rdtsc();
		routine(rargs);
		stop = rdtsc();
		stat.processing_time[tid] += (stop - start);
		//printf("p %lld\n", stop - start);
	}
	pthread_exit(NULL);
}

void launchProcessors(int num_proc, pthread_t threads[], ProcStatus pstat, ProcArgs pargs, Stack s)
{
	long t;
	int rc;
	pthread_attr_t attr;

	/* intialize locks and barriers */
	pthread_barrier_init(&pstat->bar, NULL, num_proc);
	pthread_mutex_init(&pstat->lock, NULL);
	/* set number of processors running */
	pstat->num_proc = num_proc;
	/* set all processor status as running */
	pstat->running = 0;
	for (t = 0; t < num_proc; t++)
	{
		pstat->running = pstat->running | (0x1 << t);
	}

	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
	/* create processor threads */
	for (t = 0; t < num_proc; t++)
	{
		//printf("Launching processor %ld\n", t);
		pargs[t].tid = t;
		pargs[t].s = s;
		pargs[t].pstat = pstat;
		rc = pthread_create(&threads[t], &attr, processor, (void *)&pargs[t]);
		if (rc)
		{
			printf("ERROR; return code from pthread_create() is %d\n", rc);
			exit(-1);
		}
	}
}

void collectProcessors(int num_proc, pthread_t threads[], ProcStatus pstat)
{
	long t;
	int rc;
	void *status;
	/* join processor threads */
	for(t = 0; t < num_proc; t++)
	{
		rc = pthread_join(threads[t], &status);
		if (rc)
		{
			printf("ERROR; return code from pthread_join() is %d\n", rc);
			exit(-1);
		}
		//printf("Collected thread %ld having a status of %ld\n",t,(long)status);
	}

	pthread_mutex_destroy(&pstat->lock);
	pthread_barrier_destroy(&pstat->bar);
	//pthread_exit(NULL);  
}

